import logging
import random

import tomlkit
import tomlkit.items
from smartcard.Exceptions import NoCardException

from asn1util import *
from .cardops import *
from .dataauth import *

logging.getLogger().disabled = True
logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.propagate = False
log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s %(message)s')
log_file = logging.FileHandler('pos.log', 'w', encoding='utf-8')
log_file.setFormatter(log_fmt)
logger.addHandler(log_file)
log_console = logging.StreamHandler()
log_console.setFormatter(log_fmt)
logger.addHandler(log_console)


# JR/T 0025.5-2018 A.1
_TAG_COMMENTS = {
    'TAG_50': '应用标签',  # P85
    'TAG_87': '应用优先指示器',  # P85
    'TAG_bf0c': '发卡行自定义数据（FCI）',  # JR/T 0025.3-2018 11.3.4 (P49)
    'TAG_9f38': 'PDOL',  # P96
    'TAG_9f4d': '交易日志入口',  # P95
    'TAG_df4d': '圈存日志入口',  # JR/T 0025.13-2018 5.8 (P5)
    'TAG_9f66': '终端交易属性 Terminal Transaction Qualifiers (TTQ)',  # P53
    'TAG_df69': '算法指示器',  #
    'TAG_9f02': '授权金额',  # P81
    'TAG_9f03': '其他授权金额',  # P81
    'TAG_9f1a': '终端国家代码',  # P81
    'TAG_95': '终端验证结果（TVR）',
    'TAG_5f2a': '交易货币代码',  # P81
    'TAG_9a': '交易日期（YYMMDD)',  # P81
    'TAG_9f21': '交易时间（HHMMSS)',  # P81
    'TAG_9c': '交易类型（00：消费）',
    'TAG_9f37': '不可预知数（随机数）',
    'TAG_9f49': 'DDOL',  # P91, JR/T 0025.7-2018 5.4.3.5 (P29)
    'TAG_8f': 'CA公钥索引（PKI）',  # P88
    'TAG_90': '发卡行公钥证书',  # P90
    'TAG_9f32': '发卡行RSA公钥指数',  # P90
    'TAG_92': '发卡行RSA公钥余数',  # P90
    'TAG_82': '应用交互特征（AIP）',  # P84
    'TAG_5f24': '应用失效日期',  # P84
    'TAG_5a': '应用主账号（PAN）',  # P85
    'TAG_9f07': '应用用途控制（AUC)',  # P85
    'TAG_8e': '持卡人验证方法（CVM）',  # P85
    'TAG_9f0d': '发卡行行为代码（IAC）-缺省',  # P92
    'TAG_9f0e': '发卡行行为代码（IAC）-拒绝',  # P92
    'TAG_9f0f': '发卡行行为代码（IAC）-联机',  # P92
    'TAG_5f28': '发卡行国家代码',  # P93
    'TAG_5f25': '应用生效日期',  # P84
    'TAG_9f63': '产品标识信息',  # P98
    'TAG_9f08': '应用版本号',  # P86
    'TAG_9f74': '电子现金发卡行授权码',  # JR/T 0025.12-2018 C.6 P54
    'TAG_9f36': '应用交易计数器（ATC）',  # P85
    'TAG_9f10': '发卡行应用数据',  # P92, JR/T 0025.12-2018 E P59
    'TAG_9f6c': '卡片交易属性（CTQ）',  # JR/T 0025.12-2018 C.6 P51
    'TAG_9f5d': '可用脱机消费金额',  # JR/T 0025.12-2018 C.6 P50
    'TAG_57': '磁道2等效数据（T2ED）',  # P98, 应用主账号（PAN)|分隔符'D'|失效日期（YYMM）|服务码|PIN验证域|自定义数据|补'F‘
    'TAG_5f34': '应用主账号序列号',  # P85
    'TAG_94': '应用文件定位器（AFL）',  # 84
    'TAG_9f46': 'IC卡RSA公钥证书',  # P92
    'TAG_9f47': 'IC卡RSA公钥指数',  # P92
    'TAG_9f48': 'IC卡RSA公钥余数',  # P92
    'TAG_9f4a': '静态数据认证标签列表',  # P97
    'TAG_9f4b': '签名的动态应用数据',  # P97
    'TAG_93': '签名的静态应用数据',  # P105
    'TAG_9f26': '应用密文（AC）',  # P100
    'TAG_9f27': '密文信息数据',  # 0025.12-2018 C.6 P50
    'TAG_9f68': '卡片附加处理',  # JR/T 0025.12-2018 7.7.1 (P23)
    'TAG_9f69': '卡片认证相关数据',  # JR/T 0025.12-2018 B.2 (P46)
    'TAG_DF4D': '圈存日志入口',  # JR/T 0025.13-2018 5.1 (P4)
    'TAG_8c': '卡片风险管理数据对象列表1（CDOL1）',  # P86, JR/T 0025.4-2018 6.2.3.2 (P15)
    'TAG_8d': '卡片风险管理数据对象列表2（CDOL2）',  # P86, JR/T 0025.4-2018 6.2.3.2 (P15)
}


class DataFormatException(Exception):
    """在卡数据格式不符合标准时抛出的异常
    """
    def __init__(self, message: str, data: bytes):
        super().__init__(self, message)
        self._data = data

    @property
    def data(self) -> bytes:
        return self._data


class ProcessException(Exception):
    """在处理流程或数据不符合标准时抛出的异常
    """

    def __init__(self, message: str, data: Optional[bytes]):
        super().__init__(self, message)
        self._data = data


def _tag_name(tag_octets: bytes):  # 记录在toml文档中标签的键名
    return f'TAG_{tag_octets.hex()}'


def _toml_append(toml_item: tomlkit.items.Table, tag_octets: bytes, value_octets: bytes):  # 将标签和对应值写到toml文档中
    tag_name = _tag_name(tag_octets)
    toml_item.append(tag_name, value_octets.hex())
    if tag_name in _TAG_COMMENTS:
        toml_item[tag_name].comment(_TAG_COMMENTS[tag_name])


def _parse_dol(dol_data: bytes) -> list[Tuple[bytes, int]]:
    # JR/T 0025.4-2018 5.3 (P4)
    buffer = BytesIO(dol_data)
    list_tl = []
    while True:
        if (tag := Tag.decode(buffer)) is None:
            break
        if (length := Length.decode(buffer)) is None:
            raise Exception(f"PDOL格式错误，标签{tag}未跟随长度")
        list_tl.append((bytes(tag.octets), length.value))
    return list_tl


def _parse_cn(octets: bytes) -> str:
    hex_str = octets.hex()
    for i, ch in enumerate(hex_str):
        if ch == 'f':
            return hex_str[0:i]
    return hex_str


def _ttq(contactless_debit_credit: bool = False, q_pboc: bool = True):
    # 终端交易属性TTQ
    # JR/T 0025.12-2018 6.4.4 (P12)
    # TODO
    # 第1字节：RFU, 非接借贷, qPBOC, 接触借贷, 仅脱机, 支持联机PIN, 支持签名, 支持联机授权交易的脱机数据认证
    # 默认： 0b 0010 1011
    byte1 = (0x40 if contactless_debit_credit else 0x00) | (0x20 if q_pboc else 0x00) | 0x0b
    # byte1 = 0b00101011
    # 第2字节：要求联机密文, 要求CVM, b6-b1 RFU
    byte2 = 0x80
    byte3 = 0x00
    byte4 = 0x80  # 版本01的fDDA
    return bytes([byte1, byte2, byte3, byte4])


CVM_TYPES = {0x00: 'CVM失败处理', 0x01: '卡片执行明文PIN核对', 0x02: '联机加密PIN验证',
             0x03: '卡片执行明文PIN核对+签名（纸上）', 0x04: '保留', 0x05: '保留',
             0x1e: '签名（纸上）', 0x1f: '不执行CVM'}

CVM_COND_CODES = {
    0x00: '总是', 0x01: '如果是ATM现金交易', 0x02: '如果不是ATM现金或有人值守现金或返现交易',
    0x03: '如果终端支持这个CVM',
    0x04: '如果是人工值守现金交易', 0x05: '如果是返现交易',
    0x06: '如果交易货币等于应用货币代码而且小于X值', 0x07: '如果交易货币等于应用货币代码而且大于X值',
    0x08: '如果交易货币等于应用货币代码而且小于Y值', 0x09: '如果交易货币等于应用货币代码而且大于Y值'
}


def _cvm(cvm_code: int, cvm_cond_code: int):
    # JR/T 0025.5-2018 A.1 (P87)
    spec = 'PBOC3.0规范' if cvm_code & 0x80 == 0 else '自定义'
    proc = '如果此CVM失败则持卡人验证失败' if cvm_code & 0x40 == 0 else '如果此CVM失败则应用后续的CVM'

    cvm_type = cvm_code & 0x3f
    if cvm_type in CVM_TYPES:
        ct = CVM_TYPES[cvm_type]
    elif 0x06 <= cvm_type <= 0x1d:
        ct = '保留给加入的支付系统'
    elif 0x20 <= cvm_type <= 0x2f:
        ct = '保留给各自独立的支付系统'
    elif 0x30 <= cvm_type <= 0x3e:
        ct = '保留给发卡行'
    else:
        ct = 'RFU'

    if cvm_cond_code in CVM_COND_CODES:
        ccc = CVM_COND_CODES[cvm_cond_code]
    elif 0x0a <= cvm_cond_code <= 0x7f:
        ccc = 'RFU'
    else:
        ccc = '保留给各个支付系统'

    return spec, proc, ct, ccc


def _cid(cid: int):
    # JR/T 0025.12-2018 C (P52)
    b8b7 = ('AAC', 'TC', 'ARQC', 'AAR')[cid >> 6]
    b4 = '无需通知' if cid & 0x80 == 0 else '需要通知'
    b2b1 = ('无信息', '不允许服务', 'PIN尝试次数超过', '发卡行认证失败')[cid & 0x03] if cid & 0x40 == 0 else 'RFU'
    return b8b7, b4, b2b1


def _aip(aip: int):
    aip_exp = {
        '支持SDA': aip & 0x40 != 0,
        '支持DDA': aip & 0x20 != 0,
        '支持持卡人验证': aip & 0x10 != 0,
        '支持终端风险管理': aip & 0x08 != 0,
        '支持发卡行认证': aip & 0x04 != 0,
        '支持CDA': aip & 0x01 != 0,
    }
    ret = []
    for k, v in aip_exp.items():
        ret.append(k if v else '不' + k)
    return ret


# JR/T 0025.12-2018 7.3.3 (P18), 7.3.4 (P18)
PDOL_DEFAULTS = {
    # b'\x9f\x66': bytes.fromhex('2A 00 00 80'),  # 终端交易属性TTQ： JR/T 0025.12-2018 6.4.4 表3 (P12)
    b'\x9f\x02': bytes.fromhex('00 00 00 00 00 00'),  # 授权金额
    b'\x9f\x03': bytes.fromhex('00 00 00 00 00 00'),  # 其他金额
    b'\x9f\x1a': bytes.fromhex('01 56'),  # 终端国家代码：中国
    b'\x95': bytes.fromhex('00 00 00 00 00'),  # 终端验证结果TVR
    b'\x5f\x2a': bytes.fromhex('01 56'),  # 交易货币代码：人民币
    b'\xdf\x69': bytes.fromhex('00'),  # 算法指示器：RSA/SHA1
    b'\x9c': bytes.fromhex('00'),  # 交易类型
    # b'\x9f\x37': random.randbytes(4)  # 不可预知数
    b'\xdf\x60': bytes.fromhex('00')  # CAPP交易指示位 JR/T 0025.14-2018 E.1
}


class PosProcess:
    def __init__(self, force_qpboc=False):
        self._reader = get_reader()
        if self._reader is None:
            logger.error('未找到指定的读卡器，请检查readers.json文件')
            raise Exception('未找到指定的读卡器，请检查readers.json文件')
        self._conn = None
        self._toml = None
        self._force_qpboc = force_qpboc

        self._aid = None
        self._gpo_pdol = None
        self._gpo_resp = None

        self._pan = None
        self._valid_util = None
        self._issuer_id = None

        self._aip = None
        self._cid = None
        self._afl_data = None
        self._sda_data = None
        self._sda_octets = None

        self._is_qpboc = False
        self._is_dda = True
        self._is_sda = True

        self._issuer_cert_struct = None
        self._issuer_cert_n_octets = None
        self._issuer_cert_e_octets = None

        self._sda_signed = None
        self._sda_passed = False
        self._sda_struct = None

        self._iccard_cert_struct = None
        self._iccard_cert_n_octets = None
        self._iccard_cert_e_octets = None

        self._dda_signed = None
        self._dda_passed = False
        self._dda_struct = None

    def start(self, log_file='pos-process.toml'):
        self._conn = self._reader.createConnection()
        self._toml = tomlkit.TOMLDocument()
        try:
            try:
                self._conn.connect()
                self._atr()

                # 主要处理流程
                # 选择PPSE
                logger.info("========卡片处理开始========")
                app_entries = self._select_ppse()
                # 按顺序选择AID并逐个处理
                for app_entry in app_entries:
                    # 选择AID
                    self._aid = app_entry[0]
                    app_data = self._select_aid(self._aid)
                    if app_data is None:
                        continue
                    if b'\x9f\x38' not in app_data:
                        logger.error('PDOL未出现在SELECT AID命令返回数据中，本次应用处理结束')
                        continue
                    pdol = app_data[b'\x9f\x38']

                    # GPO
                    # JR/T 0025.12-2018 (P19)
                    self._gpo_pdol, self._gpo_resp = self._gpo(pdol, not self._force_qpboc)
                    if not self._gpo_resp:
                        logger.error("GPO响应失败，本次应用处理结束")
                        continue

                    if b'\x94' not in self._gpo_resp:
                        logger.error('GPO命令相应中无应用文件定位器AFL，本次应用处理结束')
                        continue

                    # 读取AFL记录，提取其中参与SDA认证的数据
                    afl = self._gpo_resp[b'\x94']
                    self._read_afl_records(afl)

                    # 处理GPO命令及返回数据中的若干参数
                    self._explain()

                    # 判断是否为qPBOC，如果是则采用fDDA；否则为非接触式借贷应用采用标准DDA
                    # JR/T 0025.12-2018 图5 (P14)

                    self._restore_issuer_cert()
                    if self._is_sda and not self._do_sda():
                        logger.error("静态数据认证SDA失败")
                        continue

                    if not self._is_dda:
                        logger.error("卡片不支持动态数据认证DDA")
                        continue

                    self._restore_iccard_cert()
                    if self._is_qpboc:
                        self._do_fast_dda()
                    else:
                        self._do_standard_dda()
                    if not self._dda_passed:
                        logger.error("动态数据认证DDA失败")

            except NoCardException as nce:
                logger.error(f"读卡器未读取到卡片(0x"
                             f"{int.to_bytes(nce.hresult + (1 << 32), length=4, byteorder='big', signed=False).hex()})")

            finally:
                self._conn.disconnect()
        finally:
            with open(log_file, 'w', encoding='utf-8') as toml_file:
                tomlkit.dump(self._toml, toml_file)

    def _atr(self):
        atr = self._conn.getATR()
        logger.debug("ATR: {}".format(bytes(atr).hex()))
        # logger.debug("historical bytes: " + bytes(atr.getHistoricalBytes()).hex(' '))
        # logger.debug("checksum: " + "0x%X" % atr.getChecksum())
        # logger.debug("checksum OK: " + atr.checksumOK)
        # logger.debug("T0  supported: " + atr.isT0Supported())
        # logger.debug("T1  supported: " + atr.isT1Supported())
        # logger.debug("T15 supported: " + atr.isT15Supported())

    def _request(self, cadpu: CAdpu):
        return request(self._conn, cadpu)

    @staticmethod
    def _parse_df_fci(data, prefix=''):
        tag_6f = read_next_tlv(data)
        if tag_6f[0] != b'\x6f':
            raise DataFormatException(f'{prefix} 无FCI模板（6F）', data)
        buffer = BytesIO(tag_6f[2])
        tag_84 = read_next_tlv(buffer)
        if tag_84[0] != b'\x84':
            raise DataFormatException(f'{prefix} 无PPSE名称（84）', tag_6f[2])
        tag_a5 = read_next_tlv(buffer)
        if tag_a5[0] != b'\xa5':
            raise DataFormatException(f'{prefix} 无FCI专用模板名称（A5）', tag_6f[2])
        return tag_84, tag_a5

    def _select_ppse(self) -> List[Tuple[bytes, Optional[bytes], Optional[bytes], dict]]:
        """选择非接触交易环境PPSE"""
        # JR/T 0025.3-2018 11.3.4 (P48)
        # JR/T 0025.5-2018 6.4 (P11)

        toml_select_ppse = tomlkit.table()
        self._toml.add('SELECT_PPSE', toml_select_ppse)

        try:
            data = self._request(CAdpu.build(0x00, 0xa4, 0x04, 0x00, '2PAY.SYS.DDF01'.encode()))
        except RAdpuException as ex:
            if ex.radpu.sw == b'\x6a\x82':
                logger.error('卡片不支持PPSE目录选择')
            else:
                logger.error(f'选择PPSE错误：{ex.radpu}')
            return []  # TODO

        logger.debug(f'SELECT PPSE:\n{asn1_print_items(asn1_decode(data))}')

        # JR/T 0025.12-2018 6.4.3 (P11)
        tag_84, tag_a5 = PosProcess._parse_df_fci(data, 'SELECT PPSE:')
        toml_select_ppse.add('PPSE', f'{tag_84[2].hex()}({tag_84[2].decode()})')
        tag_bf0c = read_next_tlv(tag_a5[2])
        if tag_bf0c[0] != b'\xbf\x0c':
            raise DataFormatException('SELECT PPSE：无FCI发卡行自定义数据（BF0C）', tag_a5[2])

        app_entries = []
        for tag_61 in iter_tlvs(tag_bf0c[2]):
            tag_4f = None
            tag_50 = None
            tag_87 = None
            others = {}
            for at in iter_tlvs(tag_61[2]):
                if at[0] == b'\x4f':
                    tag_4f = at[2]
                elif at[0] == b'\x50':
                    tag_50 = at[2]
                elif at[0] == b'\x87':
                    tag_87 = at[2]
                else:
                    others[at[0]] = at[2]
            if tag_4f is None:
                raise DataFormatException('SELECT PPSE: 目录入口（61）的AID不存在', tag_61[2])
            app_entries.append((tag_4f, tag_87, tag_50, others, ))

            toml_tag_61 = tomlkit.table()  # 将每个目录入口数据都记入数据文件
            toml_select_ppse.add(tag_4f.hex(), toml_tag_61)
            _toml_append(toml_tag_61, b'\x4f', tag_4f)
            if tag_87:
                _toml_append(toml_tag_61, b'\x87', tag_87)
            if tag_50:
                _toml_append(toml_tag_61, b'\x50', tag_50)
            for at in others:
                _toml_append(toml_tag_61, at, others[at])

        # 如果存在多个AID，则按照应用优先指示器进行排序
        # JR/T 0025.12-2018 6.4 (P10)
        if len(app_entries) > 1:
            for aid in app_entries:
                if aid[1] is None:
                    logger.warning('卡片上存在多个应用入口但未指明应用优先级')
                else:
                    assert aid[1] & 0xf0 == 0  # 表2注
        app_entries.sort(key=lambda a: a[1])
        return app_entries

    def _select_aid(self, aid: bytes) -> Optional[dict]:
        # JR/T 0025.3-2018 11.3.4 (P49)
        toml_select_aid = tomlkit.table()
        self._toml.add('SELECT_AID', toml_select_aid)

        try:
            data = self._request(CAdpu.build(0x00, 0xa4, 0x04, 0x00, aid))
        except RAdpuException as ex:  # JR/T 0025.6-2018 7.2.3 (P11)
            if ex.radpu.sw == b'\x6a\x81':
                logger.error(f'选择AID错误（卡片被锁或命令不支持）：{ex.radpu} (AID: {aid.hex()})')
            elif ex.radpu.sw == b'\x6a\x82':
                logger.error(f'选择AID错误（所选的文件未找到）：{ex.radpu} (AID: {aid.hex()})')
            elif ex.radpu.sw == b'\x6a\x83':
                logger.error(f'选择AID错误（选择文件无效）：{ex.radpu} (AID: {aid.hex()})')
            else:
                logger.error(f'选择AID错误：{ex.radpu} (AID: {aid.hex()})')
            return None

        logger.debug(f'SELECT AID:\n{asn1_print_items(asn1_decode(data))}')
        # JR/T 0025.3-2018 11.3.4 (P49)
        tag_84, tag_a5 = PosProcess._parse_df_fci(data, 'SELECT AID:')
        logger.info(f'AID DF名：{tag_84[2].hex()}')
        _toml_append(toml_select_aid, tag_84[0], tag_84[2])
        res = {}
        for t, l, v in iter_tlvs(tag_a5[2]):
            _toml_append(toml_select_aid, t, v)
            res[t] = v
            if t == b'\xbf\x0c':  # 发卡行自定义数据（FCI）
                for ct in iter_tlvs(v):
                    _toml_append(toml_select_aid, ct[0], ct[2])

        return res

    def _gpo(self, pdol: bytes, ctl_dc: bool = False):
        toml_gpo = tomlkit.table()
        self._toml.add('GPO', toml_gpo)

        # 根据PDOL标签设置GPO命令数据
        list_tl = _parse_dol(pdol)  # JR/T 0025.12-2018 6.5.1 (P13)
        if len(list_tl) == 0:
            logger.error("PDOL未包含任何数据项")
            return None
        elif b'\x9f\x66' not in (t for t, l in list_tl):
            logger.error("PDOL未包含9f66数据项")
            return None

        toml_gpo_pdol = tomlkit.table()
        toml_gpo.add("PDOL", toml_gpo_pdol)

        pdol_data = {}
        pdol_octets = bytearray()
        for tag, length in list_tl:
            if tag in PDOL_DEFAULTS:
                pv = PDOL_DEFAULTS[tag]
            elif tag == b'\x9f\x66':
                pv = _ttq(ctl_dc)
            elif tag == b'\x9a':
                pv = bytes.fromhex(datetime.strftime(datetime.now(), '%y%m%d'))  # 交易日期
            elif tag == b'\x9f\x21':
                pv = bytes.fromhex(datetime.strftime(datetime.now(), '%H%M%S'))  # 交易日期
            elif tag == b'\x9f\x37':
                pv = random.randbytes(4)
            else:
                raise Exception(f'未知的PDOL标签{tag.hex()}')
            if len(pv) != length:
                raise Exception(f"PDOL标签{tag.hex()}值{pv.hex()}与长度{length}不符")
            pdol_data[tag] = pv
            pdol_octets.extend(pv)
            _toml_append(toml_gpo_pdol, tag, pv)

        # JR/T 0025.5-2018 B.8.3 (P116)
        enc = StreamEncoder()
        enc.append_primitive(b'\x83', pdol_octets)

        try:
            gpo_resp_data = self._request(CAdpu.build(0x80, 0xa8, 0x00, 0x00, enc.data))
            logger.debug(f'GPO:\n{asn1_print_items(asn1_decode(gpo_resp_data))}')
        except RAdpuException as ex:
            logger.error(f'GPO命令错误：{ex.radpu}')
            return None
        except Exception as ex:
            logger.exception(ex)
            return None

        toml_gpo.add("GPO_RESPONSE", gpo_resp_data.hex())

        toml_gpo_data = tomlkit.table()
        toml_gpo.add("DATA", toml_gpo_data)

        t, l, v = read_next_tlv(gpo_resp_data)
        pdol_resp = {}
        if t == b'\x80':  # 标签80开头的简单格式，仅限于借贷应用
            pdol_resp = {b'\x82': v[0:2], b'\x94': v[2:]}  # JR/T 0025.5-2018 B8 (P116)
            for t, v in pdol_resp.items():
                _toml_append(toml_gpo_data, t, v)
        elif t == b'\x77':  # 标签77开头的BER-TLV格式
            pdol_resp = {}
            for t, l, v in iter_descendant_tlvs(v):
                if t not in pdol_resp:
                    pdol_resp[t] = v
                else:
                    logger.warning(f'GPO返回中出现重复标签{t.hex()}')
                _toml_append(toml_gpo_data, t, v)
        else:
            logger.error(f'无法识别的GPO返回数据标签{t.hex()}')
        return pdol_data, pdol_resp

    def _read_afl_records(self, afl: bytes):
        assert len(afl) % 4 == 0

        toml_afl = tomlkit.table()
        self._toml.add('AFL', toml_afl)

        toml_afl_records = tomlkit.table()
        toml_afl.add('RECORDS', toml_afl_records)
        toml_afl_sda = tomlkit.table()
        toml_afl.add('SDA', toml_afl_sda)

        afl_recs = []
        self._sda_data = []
        for i in range(0, len(afl), 4):  # 依次读取指定SFI文件的指定记录
            p2 = afl[i] | 0x04
            start = afl[i + 1]
            end = afl[i + 2]
            sda_count = afl[i + 3]

            for r in range(start, end + 1):
                try:
                    record_data = self._request(CAdpu.build(0x00, 0xb2, r, p2, le=0))
                except RAdpuException as ex:
                    logger.error(f'GPO命令错误：{ex.radpu}')
                    return None
                except Exception as ex:
                    logger.exception(ex)
                    return None

                sfi = p2 >> 3
                # 在TOML文件中记录原始数据：格式为SFIxRySs，x为SFI，y为记录号，s为是否参与SDA
                toml_afl_records.add(f'SFI{sfi}R{r}S{1 if sda_count > 0 else 0}', record_data.hex())

                if sda_count > 0:
                    # 参与SDA的AFL目录
                    logger.debug(f'{sfi:x} {r} {record_data.hex()}')  # TODO
                    afl_recs.append((True, record_data, sfi, r))
                    if sfi < 11:  # 对于SFI从1-10的文件，记录的Tag（70）和记录长度不用于脱机认证处理
                        t, l, v = read_next_tlv(record_data)
                        if t != b'\x70':
                            logger.error(f"AFL文件记录不是标签70，脱机数据执行并失败[{t.hex()}]")
                            return None
                        self._sda_data.append(v)
                        toml_afl_sda.add(f'SFI{sfi}R{r}', v.hex())
                    else:  # 对于SFI从11-31的文件，记录的Tag（70）和记录长度用于脱机认证处理
                        self._sda_data.append(record_data)
                        toml_afl_sda.add(f'SFI{sfi}R{r}', record_data.hex())
                    sda_count -= 1
                else:
                    logger.debug(f'{sfi:x} {r} {record_data.hex()}')  # TODO
                    afl_recs.append((False, record_data, sfi, r))

        # 解析并保存AFL的数据
        toml_afl_data = tomlkit.table()
        toml_afl.add('DATA', toml_afl_data)

        # JR/T 0025.7-2018 5.1 (P8)
        self._afl_data = {}
        for item in afl_recs:
            t, l, v = read_next_tlv(item[1])
            if t != b'\x70':
                logger.error(f"AFL文件记录不是标签70，脱机数据执行并失败[{t.hex()}]")
                return None
            for t, l, v in iter_descendant_tlvs(v):
                if t in self._afl_data:
                    logger.warning(f'发现重复的AFL标签{t.hex()}')
                else:
                    self._afl_data[t] = v
                _toml_append(toml_afl_data, t, v)

        # 链接参与SDA的数据
        sda_buffer = bytearray()
        for s in self._sda_data:
            sda_buffer.extend(s)
        # 检查静态数据认证标签列表（标签9f4a），看AIP是否参与签名
        # JR/T 0025.7-2018 5.3.3.4 (P15), 5.4.3.1 (P25)
        # JR/T 0025.8-2018 9.4.2 (P21)
        if b'\x9f\x4a' in self._afl_data:
            tag_9f4a = self._afl_data[b'\x9f\x4a']
            if tag_9f4a != b'\x82':
                logger.error("静态数据认证标签列表存在，且包含非82的标签")  # JR/T 0025.7-2018 5.4.3.4 (P28)
                raise DataFormatException("静态数据认证标签列表（9F4A)存在，且包含非82的标签", tag_9f4a)
            else:
                sda_buffer.extend(self._gpo_resp[b'\x82'])
        self._sda_octets = bytes(sda_buffer)

    def _explain(self):
        toml_card_info = tomlkit.table()
        self._toml.add('CARD_INFO', toml_card_info)

        # 判断是否按照qPBOC处理
        # JR/T 0025.2-2018 6.5.4 (P15)
        if b'\x9f\x26' in self._gpo_resp:  # or b'\x9f\x26' in self._afl_data:
            self._is_qpboc = True
        toml_card_info.add('Q_PBOC', self._is_qpboc)

        # 磁道2等效数据（T2ED）
        if b'\x57' in self._gpo_resp:
            tag_57_hex = self._gpo_resp[b'\x57'].hex()
            for i, ch in enumerate(tag_57_hex):
                if ch == 'd':
                    self._pan = tag_57_hex[0:i]
                    self._valid_util = tag_57_hex[i + 1:i + 5]
                    toml_card_info.add("T2ED主账号", self._pan)
                    toml_card_info.add("T2ED有效期", self._valid_util)
                    break

        # 应用交互特征AIP，标签82
        # JR/T 0025.12-2018 C.6 (P51)
        if b'\x82' in self._gpo_resp:
            self._aip = self._gpo_resp[b'\x82']
            logger.debug(f'应用交互特征AIP（标签82）：{self._aip.hex(" ")}')
            assert len(self._aip) == 2
            self._is_sda = (self._aip[0] & 0x40 != 0)
            self._is_dda = (self._aip[0] & 0x20 != 0)
            toml_card_info.add('AIP', _aip(self._aip[0]))
        else:
            logger.warning(f'应用交互特征AIP（标签82）不存在')

        # 密文信息数据（CID），标签9f27
        # JR/T 0025-2018 7.4.4 (P20)
        if self._is_qpboc:
            t_9f27 = b'\x9f\x27'
            if t_9f27 in self._gpo_resp:
                self._cid = self._gpo_resp[t_9f27]
            else:
                logger.debug("缺少密文信息数据（9F27），按照JR/T 0025-2018 7.4.4构建")
                if b'\x9f\x10' in self._gpo_resp:
                    tag_9f10 = self._gpo_resp[b'\x9f\x10']
                    if len(tag_9f10) < 5:
                        logger.warning("缺少密文信息数据（9F27），且发卡行应用数据（9F10）长度不足")
                    else:
                        # 发卡行应用数据（DF10）
                        # JR/T 0015.12-2018 E (P59)
                        self._cid = bytes([tag_9f10[4] & 0x30 | 0x00])
                        logger.debug(f"构建密文信息数据（9F27）：{self._cid.hex()}")
                        self._gpo_resp[b'\x9f\x27'] = self._cid
                        _toml_append(self._toml.get('GPO').get('DATA'), b'\x9f\x27', self._cid)
                else:
                    logger.warning("缺少密文信息数据（9F27），且发卡行应用数据（9F10）不存在")

            if self._cid:
                toml_card_info.add("密文信息数据（CID）", _cid(self._cid[0]))

        # 检查AFL记录中的应用主账号（标签5A）与GPO返回的磁道2等效数据（标签57）是否一致
        pan = _parse_cn(self._afl_data[b'\x5a'])
        if self._pan is None:
            self._pan = pan
        else:
            assert self._pan == pan, 'AFL记录中的应用主账号与磁道2等效数据中的不一致'

        # 持卡人验证方法（CVM）解析
        if b'\x8e' in self._afl_data:
            tag_8e = self._afl_data[b'\x8e']
            assert (len(tag_8e) - 8) % 2 == 0
            amount_x = int.from_bytes(tag_8e[0:4], byteorder="big", signed=False)
            toml_card_info.add(f'CVM_AMOUNT_X', f'{amount_x} ({tag_8e[0:4].hex()})')
            amount_y = int.from_bytes(tag_8e[4:8], byteorder="big", signed=False)
            toml_card_info.add(f'CVM_AMOUNT_Y', f'{amount_y} ({tag_8e[4:8].hex()})')

            for i in range((len(tag_8e) - 8) // 2):
                toml_card_info.add(f'CVM{i + 1}_CODE', _cvm(tag_8e[2 * i + 8], tag_8e[2 * i + 9]))

    def _restore_issuer_cert(self) -> bool:
        # 恢复发卡行证书
        tag_8f = self._afl_data[b'\x8f']  # CA公钥索引
        tag_90 = self._afl_data[b'\x90']  # 发卡行公钥证书
        tag_92 = self._afl_data[b'\x92'] if b'\x92' in self._afl_data else None  # 发卡行RSA公钥余数
        if tag_92 and len(tag_92) == 0:
            tag_92 = None

        self._issuer_cert_e_octets = self._afl_data[b'\x9f\x32']  # 发卡行RSA公钥指数

        toml_issuer_cert = tomlkit.table()
        self._toml.add('ISSUER_CERT', toml_issuer_cert)
        # 验证发卡行证书
        self._issuer_cert_n_octets, self._issuer_cert_struct = restore_issuer_cert(
            tag_8f, tag_90, self._issuer_cert_e_octets, tag_92)
        if self._issuer_cert_struct is None:
            logger.error("发卡行证书结构解析错误")
            return False

        toml_issuer_cert.update({k: v.hex() for k, v in self._issuer_cert_struct.items()})

        # 交叉检查数据
        issuer_id_hex = self._issuer_cert_struct["发卡行标识"].hex()
        for ind, ch in enumerate(issuer_id_hex):
            if ch == 'f':
                self._issuer_id = issuer_id_hex[0:ind]
                break
        else:
            self._issuer_id = issuer_id_hex
        assert self._pan.startswith(self._issuer_id), '应用主账号与发卡行标识不匹配'

        return self._issuer_cert_n_octets is not None

    def _restore_iccard_cert(self) -> bool:
        toml_issuer_cert = tomlkit.table()
        self._toml.add('ICCARD_CERT', toml_issuer_cert)

        # 恢复IC卡证书
        tag_9f46 = self._afl_data[b'\x9f\x46']  # IC卡RSA公钥证书
        tag_9f47 = self._afl_data[b'\x9f\x47']  # IC卡RSA公钥指数
        tag_9f48 = self._afl_data[b'\x9f\x48'] if b'\x9f\x48' in self._afl_data else None  # IC卡RSA公钥余数
        if tag_9f48 and len(tag_9f48) == 0:
            tag_9f48 = None

        self._iccard_cert_e_octets = tag_9f47

        self._iccard_cert_n_octets, self._iccard_cert_struct = restore_iccard_cert(
            self._issuer_cert_n_octets, self._issuer_cert_e_octets,
            tag_9f46, tag_9f48, tag_9f47, self._sda_octets)

        if self._iccard_cert_struct is None:
            logger.error("IC卡证书结构解析错误")
            return False
        toml_issuer_cert.update({k: v.hex() for k, v in self._iccard_cert_struct.items()})

        # 交叉检查数据
        pan = _parse_cn(self._iccard_cert_struct["应用主账号"])
        assert self._pan == pan, '证书中的应用主账号（PAN）与其他数据中的不匹配'

        return self._iccard_cert_n_octets is not None

    def _do_sda(self) -> bool:
        # JR/T 0025.7-2018 5.3.3.4 (P15)
        logger.debug("========静态数据验证SDA========")
        toml_sda = tomlkit.table()
        self._toml.add('SDA', toml_sda)

        if b'\x93' in self._afl_data:
            self._sda_signed = self._afl_data[b'\x93']
        else:
            raise ProcessException("未找到签名的静态认证数据（标签93)", None)

        self._sda_struct = do_sda(self._issuer_cert_n_octets, self._issuer_cert_e_octets,
                                  self._sda_signed, self._sda_octets)
        if self._sda_struct is not None:
            toml_sda.update({k: v.hex() for k, v in self._sda_struct.items()})
            self._sda_passed = True
        else:
            self._sda_passed = False
        return self._sda_passed

    def _do_fast_dda(self):
        toml_fdda = tomlkit.table()
        self._toml.add('FDDA', toml_fdda)

        if b'\x9f\x69' in self._afl_data:
            fdda_ver = 0x01
        else:
            fdda_ver = 0x00

        toml_fdda.add("VERSION", f'{fdda_ver:02x}')
        logger.info(f"fDDA版本{fdda_ver:02x}")

        # JR/T 0025.12-2018 7.5 (P21)
        # 动态签名可以在 GPO 响应中返回，也可以在 READ RECORD 命令中返回
        if b'\x9f\x4b' in self._gpo_resp:
            tag_9f4b = self._gpo_resp[b'\x9f\x4b']
        elif b'\x9f\x4b' in self._afl_data:
            tag_9f4b = self._afl_data[b'\x9f\x4b']
        else:
            logger.error(f'动态应用数据（9f4b）未找到')
            return

        self._dda_signed = tag_9f4b
        # 验证签名的动态应用数据（fDDA）
        tag_9f37 = self._gpo_pdol[b'\x9f\x37']
        tag_9f02 = self._gpo_pdol[b'\x9f\x02']
        tag_5f2a = self._gpo_pdol[b'\x5f\x2a']
        tag_9f69 = self._afl_data[b'\x9f\x69']

        self._dda_struct = do_fdda(self._iccard_cert_n_octets, self._issuer_cert_e_octets, tag_9f4b,
                                   tag_9f37, tag_9f02, tag_5f2a, tag_9f69, fdda_ver)

        if self._dda_struct is not None:
            toml_fdda.update({k: v.hex(' ') for k, v in self._dda_struct.items()})
            self._dda_passed = True
        else:
            self._dda_passed = False
        return self._dda_passed

    def _do_standard_dda(self):
        toml_dda = tomlkit.table()
        self._toml.add('DDA', toml_dda)

        # INTERNAL AUTHENTICATION 命令
        if b'\x9f\x49' in self._afl_data:
            ddol = _parse_dol(self._afl_data[b'\x9f\x49'])
        else:
            ddol = [(b'\x9f\x37', 4)]

        toml_int_auth = tomlkit.table()
        self._toml.add('INT_AUTH', toml_int_auth)
        ddol_data = bytearray()
        for t, l in ddol:
            if t == b'\x9f\x37':
                assert l == 4
                val = random.randbytes(4)
            else:
                raise Exception(f"未知的DDOL标签{t.hex()}")
            ddol_data.extend(val)
            _toml_append(toml_int_auth, t, val)
        toml_int_auth.add('INT_AUTH_DDOL', ddol_data.hex())

        try:
            resp_data = self._request(CAdpu.build(0x00, 0x88, 0x00, 0x00, bytes(ddol_data), 0x00))
            logger.debug(f'INTERNAL AUTHENTICATION:\n{asn1_print_items(asn1_decode(resp_data))}')
            t, l, v = read_next_tlv(resp_data)
            if t != b'\x80':
                raise DataFormatException(f'INTERNAL AUTHENTICATION命令错误：返回数据不是标签80', resp_data)
            toml_int_auth.add('INT_AUTH_RESP', v.hex())
        except RAdpuException as ex:
            logger.error(f'INTERNAL AUTHENTICATION命令错误：{ex.radpu}')
            return None
        except Exception as ex:
            logger.exception(ex)
            return None

        self._dda_struct = do_dda(self._iccard_cert_n_octets, self._iccard_cert_e_octets, ddol_data, v)
        if self._dda_struct is not None:
            toml_dda.update({k: v.hex() for k, v in self._dda_struct.items()})
            self._dda_passed = True
        else:
            self._dda_passed = False
        return self._dda_passed
